package it.polito.let.pages;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import it.polito.let.filesystem.*;
import it.polito.let.filesystem.FileConstants.FileType;
import it.polito.let.http.*;

public class PageFactory {

	private URLClassLoader loader;

	private Map<String,File> binaryMap;

	private static class PageFactoryHolder {
		private final static PageFactory INSTANCE = new PageFactory();
	}

	public static PageFactory getInstance() {
		return PageFactoryHolder.INSTANCE;
	}

	private PageFactory() {
		URL url;
		try {
			url = new URL("file:" + PagesConstants.DYNAMIC_BUILD_DIR + File.separator);
			loader = new URLClassLoader(new URL[] { url });
		} catch (MalformedURLException e) {}
		binaryMap = Collections.synchronizedMap(new HashMap<String,File>());
	}

	public synchronized Page getPage(Request request) throws FactoryException, PageNotFoundException, HTTPError {
		FileType type = FileType.fromExtension(FileUtil.FileExtension(request.getRequestPath()));
		if (type == FileType.JHP)
			return jhpHandler(request);
		else
			return standardHandler(request);
	}

	private Page standardHandler(Request request) throws HTTPError {
		Logger.getLogger("it.polito.let.jhpserver").info("Sending standard page");
		Page p = new StandardPage();
		if (request != null)
			p.setRequest(request);
		return p;
	}

	private Page jhpHandler(Request request) throws PageNotFoundException, FactoryException {

		String FileName = request.getRequestPath(); 
		String ClassName;

		Class<?> pClass;
		Page p = null;

		try {
			Logger.getLogger("it.polito.let.jhpserver").info("sending JHP page "+ FileName);

			File fsource = new File(PagesConstants.ROOT, FileName);

			if (!fsource.exists()) {
				Logger.getLogger("it.polito.let.jhpserver").info("Source JHP file not found");
				throw new PageNotFoundException();
			}

			if (!binaryMap.containsKey(FileName)) {
				File fbin = FileUtil.compile(fsource, PagesConstants.DYNAMIC_BUILD_DIR);
				Logger.getLogger("it.polito.let.jhpserver").info(
						fsource.getPath() + " was compiled to " + fbin.getPath() + " because binary does not exist");
				ClassName = FileUtil.changeFileExtension(fbin.getName(), null);
				pClass = loader.loadClass(ClassName);
				binaryMap.put(FileName, fbin);
			} else {
				File fbin = binaryMap.get(FileName);
				if (!fbin.exists()) {
					fbin = FileUtil.compile(fsource, PagesConstants.DYNAMIC_BUILD_DIR);
					Logger.getLogger("it.polito.let.jhpserver").info(
							fsource.getPath() + " was compiled to " + fbin.getPath() + " because binary does not exist");
					ClassName = FileUtil.changeFileExtension(fbin.getName(), null);
					pClass = loader.loadClass(ClassName);
					binaryMap.put(FileName, fbin);
				} else if (FileUtil.isOlder(fsource, fbin)) {
					Logger.getLogger("it.polito.let.jhpserver").info(
							fsource.getPath() + " was compiled to " + fbin.getPath() + " because binary is older than source");
					fbin.delete();
					fbin = FileUtil.compile(fsource, PagesConstants.DYNAMIC_BUILD_DIR);
					ClassName = FileUtil.changeFileExtension(fbin.getName(), null);
					pClass = loader.loadClass(ClassName);
					binaryMap.put(FileName, fbin);
				} else {
					ClassName = FileUtil.changeFileExtension(fbin.getName(), null);
					pClass = loader.loadClass(ClassName);
					binaryMap.put(FileName, fbin);
				}
			}
			p = (Page) pClass.newInstance();
			if (request != null)
				p.setRequest(request);

		} catch (FileNotFoundException fnfe) {
			throw new PageNotFoundException(fnfe.getMessage());
		} catch (FileGenericError fe) {
			throw new FactoryException(fe.getMessage());
		} catch (ClassNotFoundException cnfe) {
			throw new PageNotFoundException(cnfe.getMessage());
		} catch (InstantiationException e) {
			throw new FactoryException(e.getMessage());
		} catch (IllegalAccessException e) {
			throw new FactoryException(e.getMessage());
		}

		return p;
	}

}
